#include "gc9107_generic_spi.h"

#if (USE_LCD_PANEL_GC9107_SPI_WATCH && LCD_GENERIC_SPI)

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "lvgl/src/lv_core/lv_refr.h"
#include "lvgl/src/lv_core/lv_disp.h"
#include "lvgl/src/lv_hal/lv_hal_tick.h"
#include "st_lcd_spi_drv.h"
#include "../common/utils.h"
#include "../common/lv_pm.h"
#include "../../board.h"
#include "lv_conf.h"

static lv_disp_drv_t * lcd_disp_drv = NULL;

#define SPI_TRANSFER_MODE               XFER_MODE_POLL
#define GPIO_LCD_RST                    GPIO77

static const uint32_t mfp_lcd[] = {
  MFP_REG(GPIO_13) | MFP_AF0 | MFP_DRIVE_MEDIUM | MFP_PULL_HIGH | MFP_SLEEP_DIR | MFP_LPM_EDGE_NONE, /*CS0 gpio*/
  MFP_REG(GPIO_TDS_DIO9_QSPI_CS2) | MFP_AF1 | MFP_DRIVE_MEDIUM | MFP_PULL_HIGH | MFP_SLEEP_DIR | MFP_LPM_EDGE_NONE, /*RST gpio*/
  MFP_REG(GPIO_12) | MFP_AF1 | MFP_DRIVE_MEDIUM | MFP_PULL_LOW | MFP_SLEEP_DIR | MFP_LPM_EDGE_NONE, /*SCLK*/
  MFP_REG(GPIO_15) | MFP_AF1 | MFP_DRIVE_MEDIUM | MFP_PULL_LOW | MFP_SLEEP_DIR | MFP_LPM_EDGE_NONE, /*SDA*/
  MFP_REG(GPIO_14) | MFP_AF0 | MFP_DRIVE_MEDIUM | MFP_PULL_LOW | MFP_SLEEP_DIR | MFP_LPM_EDGE_NONE, /*DCX*/
  MFP_REG(GPIO_VCXO_OUT) | MFP_AF1 | MFP_DRIVE_MEDIUM | MFP_PULL_LOW | MFP_SLEEP_NONE | MFP_LPM_EDGE_NONE, /*GPIO126 PWM*/
  MFP_EOC
};
struct lcd_spi_info lcd_info = {
    .dev = SPI_BUS_SSP2,
    .spi_mode = SPI_MODE_0,
    .wclk = SPI_CLK_26MHZ,
    .rclk = SPI_CLK_812500HZ,
    .cs_pin = GPIO13,
#if LCD_SPI_4_LINE
    .protocol = INTERFACE_PROTOCOL_SPI_4_LINE,
    .dc_pin = GPIO14,
#else
    .protocol = INTERFACE_PROTOCOL_SPI_3_LINE,
#endif
    .mfp = mfp_lcd,
};
void lcdc_reset_panel(void)
{
    gpio_set_direction(GPIO_LCD_RST, GPIO_OUT);
    gpio_output_set(GPIO_LCD_RST, 1);
    mdelay(10);
    gpio_output_set(GPIO_LCD_RST, 0);
    mdelay(10);
    gpio_output_set(GPIO_LCD_RST, 1);
    mdelay(100);
}

#define RDDID                0x4
#define LCD_COLOR_DEPTH_BYTE (LV_COLOR_DEPTH / 8)

enum {
    SPI_WRITE_CMD = 0,
    SPI_WRITE_DATA,
    SPI_WRITE_DELAY
};

struct spi_write_t {
    uint8_t type;  /* SPI_WRITE_CMD / SPI_WRITE_DATA / SPI_WRITE_DELAY */
    uint8_t value;
    uint8_t data_length; /* Only for SPI_WRITE_CMD */
};

const static struct spi_write_t spi_init_poc[] = {
    { SPI_WRITE_CMD,  0xFE, 0 },
    { SPI_WRITE_CMD,  0xEF, 0 },
    { SPI_WRITE_CMD,  0x3A, 1 },
    { SPI_WRITE_DATA, 0x05, 0 },  /* Format is RGB565, if for RGB666, 0x05 -> 0x06 */
    { SPI_WRITE_CMD,  0x35, 1 },
    { SPI_WRITE_DATA, 0x00, 0 },
    { SPI_WRITE_CMD,  0xB0, 1 },
    { SPI_WRITE_DATA, 0xC0, 0 },
    { SPI_WRITE_CMD,  0xB2, 1 },
    { SPI_WRITE_DATA, 0x2F, 0 },
    { SPI_WRITE_CMD,  0xB3, 1 },
    { SPI_WRITE_DATA, 0x03, 0 },
    { SPI_WRITE_CMD,  0xB6, 1 },
    { SPI_WRITE_DATA, 0x19, 0 },
    { SPI_WRITE_CMD,  0xB7, 1 },
    { SPI_WRITE_DATA, 0x01, 0 },
    { SPI_WRITE_CMD,  0xAC, 1 },
    { SPI_WRITE_DATA, 0xC8, 0 },
    { SPI_WRITE_CMD,  0xAB, 1 },
    { SPI_WRITE_DATA, 0x07, 0 },
    { SPI_WRITE_CMD,  0xB4, 1 },
    { SPI_WRITE_DATA, 0x04, 0 },
    { SPI_WRITE_CMD,  0xA8, 1 },
    { SPI_WRITE_DATA, 0x0C, 0 },
    { SPI_WRITE_CMD,  0xB8, 1 },
    { SPI_WRITE_DATA, 0x08, 0 },
    { SPI_WRITE_CMD,  0xE8, 1 },
    { SPI_WRITE_DATA, 0x20, 0 },
    { SPI_WRITE_CMD,  0xE9, 1 },
    { SPI_WRITE_DATA, 0x44, 0 },
    { SPI_WRITE_CMD,  0xEA, 1 },
    { SPI_WRITE_DATA, 0x44, 0 },
    { SPI_WRITE_CMD,  0xEB, 1 },
    { SPI_WRITE_DATA, 0xB0, 0 },
    { SPI_WRITE_CMD,  0xED, 1 },
    { SPI_WRITE_DATA, 0x03, 0 },
    { SPI_WRITE_CMD,  0xC6, 1 },
    { SPI_WRITE_DATA, 0x3D, 0 },
    { SPI_WRITE_CMD,  0xC7, 1 },
    { SPI_WRITE_DATA, 0x10, 0 },
    { SPI_WRITE_CMD,  0xF0, 14 },
    { SPI_WRITE_DATA, 0x0F, 0 },
    { SPI_WRITE_DATA, 0x20, 0 },
    { SPI_WRITE_DATA, 0x25, 0 },
    { SPI_WRITE_DATA, 0x35, 0 },
    { SPI_WRITE_DATA, 0x26, 0 },
    { SPI_WRITE_DATA, 0x3f, 0 },
    { SPI_WRITE_DATA, 0x30, 0 },
    { SPI_WRITE_DATA, 0x60, 0 },
    { SPI_WRITE_DATA, 0x26, 0 },
    { SPI_WRITE_DATA, 0x03, 0 },
    { SPI_WRITE_DATA, 0x00, 0 },
    { SPI_WRITE_DATA, 0x09, 0 },
    { SPI_WRITE_DATA, 0x13, 0 },
    { SPI_WRITE_DATA, 0x1A, 0 },
    { SPI_WRITE_CMD,  0xF1, 14 },
    { SPI_WRITE_DATA, 0x00, 0 },
    { SPI_WRITE_DATA, 0x28, 0 },
    { SPI_WRITE_DATA, 0x7F, 0 },
    { SPI_WRITE_DATA, 0x39, 0 },
    { SPI_WRITE_DATA, 0xA5, 0 },
    { SPI_WRITE_DATA, 0x0F, 0 },
    { SPI_WRITE_DATA, 0x30, 0 },
    { SPI_WRITE_DATA, 0x60, 0 },
    { SPI_WRITE_DATA, 0x00, 0 },
    { SPI_WRITE_DATA, 0x05, 0 },
    { SPI_WRITE_DATA, 0x01, 0 },
    { SPI_WRITE_DATA, 0x0F, 0 },
    { SPI_WRITE_DATA, 0x0F, 0 },
    { SPI_WRITE_DATA, 0x1F, 0 },
    { SPI_WRITE_CMD,  0x11, 0 },
    { SPI_WRITE_DELAY, 10, 0  },
    { SPI_WRITE_CMD,  0x29, 0 },
    { SPI_WRITE_DELAY,  10, 0 }
};

static int padding_x = 0;
static int padding_y = 0;

const static struct spi_write_t spi_display_on_cmds[] = {
    { SPI_WRITE_CMD, 0x11, 0 },
    { SPI_WRITE_DELAY, 120, 0 },
    { SPI_WRITE_CMD, 0x29, 0 },
    { SPI_WRITE_DELAY, 10, 0 }
};

const static struct spi_write_t spi_display_off_cmds[] = {
    { SPI_WRITE_CMD, 0x28, 0 },
    { SPI_WRITE_DELAY, 10, 0 },
    { SPI_WRITE_CMD, 0x10, 0 },
    { SPI_WRITE_DELAY, 5, 0 }
};

#define ID_TABLE_END 0

unsigned gc9107_generic_id_table[] = {
    0x9107,
    ID_TABLE_END,
};

static uint8_t suspend_flag = 0;
static void gc9107_generic_suspend(void *arg)
{
    struct crane_panel_t *p = arg;
    printf("gc9107_suspend\n");
    /*since disp_refr can be changed by other register,
     *so if gc9107_generic suspend need use lcd_disp_drv instead of lv_refr_vdb_is_flushing()
     *refer to lv_disp_refr_task() and lv_refr_vdb_flush()*/
    if(lcd_disp_drv && lcd_disp_drv->buffer) {
        // wait flushing finish~~
        while(lcd_disp_drv->buffer->flushing)
            ;
    }
    p->panel_onoff(0);
    suspend_flag = 1;
}

static int panel_detect(void);
static void gc9107_generic_resume(void *arg)
{
    struct crane_panel_t *p = arg;
    printf("gc9107_resume\n");
    p->panel_onoff(1);
    suspend_flag = 0;

    lv_obj_invalidate(lv_disp_get_scr_act(NULL));
    lv_refr_now(_lv_refr_get_disp_refreshing());

    if(lcd_disp_drv && lcd_disp_drv->buffer) {
        // wait flushing finish~~
        int cnt = 0;
        while(lcd_disp_drv->buffer->flushing) {
            uos_sleep(1);
            cnt ++;
            if(cnt > 20) { // over 100ms
                int res = panel_detect();
                printf("gc9107_generic_resume fail!!  panel detect: %d\n", res);
                break;
            }
        }
    }
}

static void spi_write_function(const struct spi_write_t *spi_data, uint32_t length)
{
    uint16_t i = 0;
    uint8_t data_idx = 0;
    uint8_t data_length = 0;
    const struct spi_write_t *pdata = spi_data;

    if (pdata == NULL || length == 0) {
        cplog_printf("%s: %d: param is error!! \n", __FUNCTION__, __LINE__);
        return;
    }

    for (i = 0; i < length;) {
        i++;
        if (pdata->type == SPI_WRITE_CMD) {
            lcd_spi_write_cmd(pdata->value, 8);
            if (pdata->data_length > 0) {
                data_length = pdata->data_length;
                pdata++;
                for (data_idx = 0; (data_idx < data_length) && (i < length); data_idx++, i++) {
                    if (pdata->type != SPI_WRITE_DATA) {
                        cplog_printf("%s: %d: SPI data is error[%u, %u], please check!!!!!!!! \n",
                               __FUNCTION__, __LINE__, i, data_idx);
                        return;
                    }
                    else {
                        lcd_spi_write_data(pdata->value, 8);
                        pdata++;
                    }
                }
            }
            else {
                pdata++;
            }
        }
        else if (pdata->type == SPI_WRITE_DELAY) {
            mdelay(pdata->value);
            pdata++;
        }
        else {
            cplog_printf("%s: %d: SPI data is error[%u, %u], please check!!!!!!!! \n",
                   __FUNCTION__, __LINE__, i, pdata->type);
            return;
        }
    }

    return;
}

static void gc9107_generic_panel_invalid(uint32_t start_x,
                                  uint32_t start_y,  uint32_t end_x, uint32_t end_y)
{
    /* the real size of the panel is 132x132,
    * but the visible size is 128x128,
    * so padding the un-visible area.
    * */
    start_x += padding_x;
    start_y += padding_y;
    end_x += padding_x;
    end_y += padding_y;

    lcd_spi_write_cmd(0x2A, 8);
    lcd_spi_write_data(((start_x >> 8) & 0xFF), 8);
    lcd_spi_write_data((start_x & 0xFF), 8);
    lcd_spi_write_data(((end_x >> 8) & 0xFF), 8);
    lcd_spi_write_data((end_x & 0xFF), 8);

    lcd_spi_write_cmd(0x2B, 8);
    lcd_spi_write_data(((start_y >> 8) & 0xFF), 8);
    lcd_spi_write_data((start_y & 0xFF), 8);
    lcd_spi_write_data(((end_y >> 8) & 0xFF), 8);
    lcd_spi_write_data((end_y & 0xFF), 8);

    lcd_spi_write_cmd(0x2C, 8);
}

static int match_id(unsigned id, unsigned *id_table)
{
    int found = 0;
    unsigned *p = id_table;

    while (*p) {
        if (id == *p) {
            found = 1;
            break;
        }
        p++;
    }

    return (found == 1) ? 1 : 0;
}

static int gc9107_generic_spi_read_id(unsigned *id)
{
    return lcd_spi_read24(RDDID, (uint32_t *)id);
}

__attribute__ ((unused)) static int panel_detect_readid(void)
{
    unsigned id = 0;
    if (gc9107_generic_spi_read_id(&id) < 0) {
        cplog_printf("panel read id failed\n");
        goto err;
    }

    if (match_id(id, gc9107_generic_id_table) == 0) {
        cplog_printf("unknown panel id = 0x%x\n", id);
        goto err;
    }

    printf("Found LCD panel GC9107-SPI, id: 0x%x\n", id);
    printf("panel detect by readid ok\n");

    return 0;
err:
    cplog_printf("panel detect by readid failed\n");
    return -1;
}

static uint32_t rgb666_2_spi(uint8_t r, uint8_t g, uint8_t b)
{
    uint32_t val;
    val = ((b & 0x3F) << 2) | ((g & 0x3F) << 10) | ((r & 0x3F) << 18);
    return val;
}

__attribute__ ((unused)) static int panel_detect_fb(void)
{
    int res = 0;
    uint32_t val = 0, val1 = 0;

    lcd_spi_write_cmd(0x3A, 8);
    lcd_spi_write_data(0x66, 8); // to rgb666 mode

    gc9107_generic_panel_invalid(0, 0, 1, 1);

    val = rgb666_2_spi(5, 2, 3);
    lcd_spi_write_data(val, 24);

    lcd_spi_read_data(0x2E, 8, (uint32_t *)&val1, 32, 1);
    if(val == val1) {
        printf(" panel_detect_fb success!!!!\n");
        res = 0;
    } else {
        cplog_printf(" panel_detect_fb failed[0x%x, 0x%x]!!!!\n", val, val1);
        res = -1;
    }

    lcd_spi_write_cmd(0x3A, 8);
    lcd_spi_write_data(0x05, 8); // to rgb565 mode

    return res;
}

typedef int (*panel_detect_fn_t)(void);

static panel_detect_fn_t detect_fn[] = {
    panel_detect_readid,
    panel_detect_fb,
};

static int panel_detect(void)
{
    int cnt = ARRAY_SIZE(detect_fn);
    int i, ret;

    for (i = 0; i < cnt; i++) {
        ret = detect_fn[i]();
        if (ret == 0) {
            return 0;
        }
    }

    return -1;
}

static void gc9107_generic_spi_onoff(int on)
{
    uint32_t length;

    if (on) {
        length = sizeof(spi_display_on_cmds) / sizeof(struct spi_write_t);
        spi_write_function(spi_display_on_cmds, length);
    }
    else {   /* off */
        length = sizeof(spi_display_off_cmds) / sizeof(struct spi_write_t);
        spi_write_function(spi_display_off_cmds, length);
    }
}

__attribute__ ((unused)) static int gc9107_generic_spi_probe_poc(unsigned lcd_clk)
{
    lv_pm_info_t pm_info;

    lcd_spi_init(&lcd_info);

    lcdc_reset_panel();

    if (panel_detect() < 0) {
        return -1;
    }

    uint32_t length = sizeof(spi_init_poc) / sizeof(struct spi_write_t);
    spi_write_function(spi_init_poc, length);

    pm_info.suspend = gc9107_generic_suspend;
    pm_info.resume = gc9107_generic_resume;
    pm_info.data = &gc9107_spi;
    pm_info.part = LV_PM_PART_DISPLAY;
    lv_pm_register(&pm_info);

    return 0;
}

static void gc9107_generic_spi_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    lcd_disp_drv = disp_drv;
    if(suspend_flag == 1) {
        lv_disp_flush_ready(disp_drv);
        return;
    }
    // printf("flush start(%d): %d \n", SPI_TRANSFER_MODE, lv_tick_get());
    uint32_t width = area->x2- area->x1 + 1;
    uint32_t height = area->y2 - area->y1 + 1;

    gc9107_generic_panel_invalid(area->x1, area->y1, area->x2, area->y2);
    //printf("flush : %d, %d -> %d, %d (%d, %d) \n", area->x1, area->y1, area->x2, area->y2, width, height);
    lcd_spi_write_fb((uint8_t *)color_p, width * height * 2, SPI_TRANSFER_MODE);
}

struct crane_panel_t gc9107_spi = {
    .probe = gc9107_generic_spi_probe_poc,
    .readid = gc9107_generic_spi_read_id,
    .panel_onoff = gc9107_generic_spi_onoff,
    .flush = gc9107_generic_spi_flush,
};

#endif
